﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace CrossEntropyProject
{
    public static class PAM
    {

        #region Class variables

        internal static float[,] matrix ;
        internal static int k ;             //number of clusters

        internal static ArrayList[] clustersArr;  //Array of array lists
        internal static float[] sumOfDistancesForClusters ;

        internal static ArrayList Medoids ;  //Array of medoids

        internal static bool flag ;
        internal static int numOfCol ;

        internal static float minSumOfDistances ;

        #endregion


        #region void StartPam()
        internal static void StartPam()
        {
            Thread.Sleep(1000);

            flag = true;
            float min = 100;             //save the minimum distance 
            int medoidIndex = 0;          //save index of the medoid
            int minIndexOfHour = 0;       //save the index of min hour


            k = Convert.ToInt32(DataSet.mainForm.num_cluster_select.Value);
            Constants.numOfMedoids = k;
            matrix = PamMatrixInput.distanceMatrix;
            numOfCol = matrix.GetLength(0);
            Medoids = new ArrayList();  //Array for initial medoids
            sumOfDistancesForClusters = new float[k];
            clustersArr = new ArrayList[k];
            minSumOfDistances = 100;


            for (int i = 0; i < clustersArr.Length; i++)   //Memory allocation
            {
                clustersArr[i] = new ArrayList();
            }

            UpdateLogBox("Build step started...");
            Thread.Sleep(1000);

            Medoids = InitializeMedoids(matrix, k);
         
            UpdateLogBox("Initialized Medoids..."); 
            Thread.Sleep(1000);

            #region Step 1- initial allocation of objects to medoids
                //assign each object to medoid
                
                for (int i = 1; i < numOfCol; i++)
                {
                    for (int j = 0; j < Medoids.Count; j++)
                    {
                        if (Medoids.Contains(i))             //if hour i is medoid then continue
                        {
                            j = Medoids.Count;
                            min = 100;
                            flag = false;
                        }
                        else
                        {
                            if (min > matrix[i, (int)Medoids[j]])
                            {
                                min = matrix[i, (int)Medoids[j]];   //save the minimum distance
                                medoidIndex = j;
                                minIndexOfHour = i;
                                flag = true;
                            }
                        }
                    }
                    if (flag)
                    {
                        clustersArr[medoidIndex].Add(minIndexOfHour);        //Assign each dataset object hour to the nearest medoidIndex
                    }
                    min = 100;
                }
                #endregion
           

            UpdateLogBox("Allocating objects to Medoids...");
           

            Thread.Sleep(1000);
            UpdateLogBox("Swap step started...");
               
            #region Step 2- Reduce the objective function

                    for (int i = 0; i < Medoids.Count; i++)
                    {
                        FindOptimalMedoid((int)Medoids[i], i);
                    }

                    #endregion

           
            #region Step 3- Assign each dataset object to the nearest medoid
                    foreach (var arrayList in clustersArr)
                    {
                        arrayList.Clear();
                    }

                    flag = true;
                    for (int i = 1; i < numOfCol; i++)
                    {
                        for (int j = 0; j < Medoids.Count; j++)
                        {
                            if (Medoids.Contains(i))      //if hour i is medoid then continue
                            {
                                j = Medoids.Count;
                                min = 100;
                                flag = false;
                            }
                            else
                            {
                                if (min > matrix[i, (int)Medoids[j]])
                                {
                                    min = matrix[i, (int)Medoids[j]];      //save the minimum distance
                                    medoidIndex = j;
                                    minIndexOfHour = i;
                                    flag = true;
                                }
                            }
                        }
                        if (flag)
                        {
                            clustersArr[medoidIndex].Add(minIndexOfHour);        //Assign each dataset object hour to the nearest medoidIndex
                        }
                        min = 100;
                    }//for
                    #endregion
      
            DataSet.PerformDataTransformation();
            Thread.Sleep(1000);
            UpdateLogBox("Done!");

        }//StartPam
        #endregion


        #region FindOptimalMedoid(int medoid, int medoidIndex)

        public static void FindOptimalMedoid(int medoid, int medoidIndex)
        {
            int tempMedoid, count = 0, minMedoid = -1;
            float tempSum = 0, minTemp = 100;

            for (int j = 1; j < numOfCol; j++)  //for each object j that not medoid
            {
                if (Medoids.Contains(j))   //if object j is a medoid then continue
                    continue;
                else     //object j is not a medoid                          
                {
                    tempMedoid = j;
                    for (int i = 1; i < numOfCol; i++)  //for all objects i
                    {
                        for (int m = 0; m < Medoids.Count; m++)   //for all medoids
                        {
                            if ((int)Medoids[m] == medoid)     //we check if we can replace medoid to another object j to get a better results 
                                continue;
                            else
                            {
                                if (matrix[(int)Medoids[m], i] > matrix[i, tempMedoid])  //check if a[i,j] is the smallest distance between all the medoids
                                    count++;
                            }//else

                        }//for m
                        if (count == Medoids.Count - 1)  //if count=numOfMedoids-1 then sum+=a[i,j] 
                            tempSum += matrix[i, tempMedoid];
                        else
                        {
                            for (int m = 0; m < Medoids.Count; m++)
                            {
                                if ((int)Medoids[m] == medoid)
                                    continue;
                                else
                                {
                                    if (matrix[(int)Medoids[m], i] < minTemp)  //we find the minimal distance between i and the medoids
                                    {
                                        minTemp = matrix[(int)Medoids[m], i];
                                        minMedoid = (int)Medoids[m];
                                    }
                                }
                            }//for m
                            tempSum += matrix[minMedoid, i];
                        }//else
                        count = 0;
                        minTemp = 100;
                    }//for i
                }//else

                if (tempSum < minSumOfDistances)
                {
                    Medoids[medoidIndex] = tempMedoid;
                    minSumOfDistances = tempSum;
                }//if
                tempSum = 0;
            }//for j


        }//method
        #endregion


        #region InitializeMedoids()
        /// <summary>
        /// This method finds the intial optimal medoids.
        /// </summary>
        /// <param name="distanceMatrix"></param>
        /// <param name="k"></param>
        /// <returns></returns>
        public static ArrayList InitializeMedoids(float[,] distanceMatrix, int k)
        {
            ArrayList finalmedoids = new ArrayList();

            int[] medoids = new int[k + 1]; // working from index 1 , not 0

            int indexWhereToPutMedoid = 1;

            int firstMedoid = 0;
            int nextMedoid = -1;
            int numberOfrows = distanceMatrix.GetLength(0);
            int numberOfcols = distanceMatrix.GetLength(1);

            int[] selectedMedoidsArr = new int[numberOfcols];//this array will indicate which medoids where selected.   
            //the index represent the medoid, if selected then the value at that index is -1 .

            float sumOfDistances;

            //find the first medoid - the most centrally located in the set of objects.  

            for (int i = 1; i < numberOfrows; i++)   //find the minimum sum for first medoid
            {
                sumOfDistances = 0;
                for (int j = 1; j < numberOfcols; j++)
                {
                    sumOfDistances += distanceMatrix[i, j];
                }

                if (minSumOfDistances > sumOfDistances)
                {
                    minSumOfDistances = sumOfDistances;
                    firstMedoid = i;
                }
            }

            medoids[indexWhereToPutMedoid] = firstMedoid;
            selectedMedoidsArr[firstMedoid] = -1;

            float tempMin;

            for (int numOfClusters = 0; numOfClusters < k - 1; numOfClusters++)
            {

                for (int j = 1; j < numberOfrows; j++)
                {

                    if (selectedMedoidsArr[j] == -1)  //medoid
                        continue;
                    sumOfDistances = 0;

                    for (int i = 1; i < numberOfcols; i++)
                    {
                        tempMin = 100;
                        for (int l = 1; l <= indexWhereToPutMedoid; l++)
                        {
                            if (distanceMatrix[i, medoids[l]] < tempMin)
                            {
                                tempMin = distanceMatrix[i, medoids[l]];
                            }

                        }

                        if (distanceMatrix[j, i] < tempMin)
                            sumOfDistances += distanceMatrix[j, i];
                        else
                            sumOfDistances += tempMin;
                    }

                    if (minSumOfDistances > sumOfDistances)
                    {
                        minSumOfDistances = sumOfDistances;
                        nextMedoid = j;
                    }

                }//for

                indexWhereToPutMedoid++;
                medoids[indexWhereToPutMedoid] = nextMedoid;
                selectedMedoidsArr[nextMedoid] = -1;

            }//for numOfClusters

            for (int i = 1; i < medoids.Length; i++)
            {
                finalmedoids.Add(medoids[i]);
            }

            return finalmedoids;
        }
        #endregion


        #region UpdateLogBox(String msg)
        internal static void UpdateLogBox(String msg)
        {
            Object message = Environment.NewLine + msg;
            DataSet.mainForm.backgroundWorker5.ReportProgress(0, message);
        }
        #endregion


    }//class Pam

}//namespace


